﻿#region Copyright ©2008-2009, Technische Universitaet Muenchen
// ====================================================================================================
//
// Last Changed by       $Author: kalus $
// Last Changed Date     $LastChangedDate: 2009-07-29 14:54:57 +0200 (Wed, 29 Jul 2009) $
// Last Changed Revision $Rev: 258 $
//
// ====================================================================================================
#endregion

using System;
using System.Collections.Generic;
using Microsoft.SharePoint;
using System.IO;
using System.Xml;
using Microsoft.SharePoint.WebPartPages;

namespace Tum.CollabXT.SharePoint
{
    class SharePointToolProvider : IToolProvider
    {
        #region Private Attributes
        private IProcessProvider _processProvider;
        private Log _log;
        private string _webApplication;
        private string _parentSite;
        private string _siteTitle;
        private string _siteName;
        private string _templateDirectory;
        #endregion

        #region Public Properties
        internal string WebApplication
        {
           get { return _webApplication; }
           set { _webApplication = value; }
        }

        internal string ParentSite
        {
            get { return _parentSite; }
            set { _parentSite = value; }
        }

        internal string SiteName
        {
            //get { return _siteName; }
            set { _siteName = value; }
        }

        internal string SiteTitle
        {
            //get { return _siteTitle; }
            set { _siteTitle = value; }
        }

        internal string TemplateDirectory
        {
            get { return _templateDirectory; }
            set { _templateDirectory = value; }
        }
        #endregion

        #region IToolProvider Members

        public static string GetProviderName()
        {
            return Resources.Language.ProviderInfo_Name;
        }

        public static string GetProviderDescription()
        {
            return Resources.Language.ProviderInfo_Description;
        }

        public static string GetProviderAuthor()
        {
            return "© 2009 TU München";
        }

        public void Initialize(IProcessProvider processProvider, Log log)
        {
            _processProvider = processProvider;
            _log = log;
        }

        public void Process()
        {
            if (!CreateMainSite())
                return;

            if (!BuildProjectPortal())
                return;
        }

        public string GetConfigPageName()
        {
            return "SharePointConfigPage.xaml";
        }

        #endregion

        #region IProvider Members

        public Dictionary<string, object> ExtendedData
        {
            get { throw new NotImplementedException(); }
        }

        public void Serialize(XmlNode outputParentNode)
        {
           XmlDocument xmlDoc = outputParentNode.OwnerDocument;

           XmlNode settingsNode = xmlDoc.CreateElement("settings");
           outputParentNode.AppendChild(settingsNode);

           XmlAttribute templateDirectoryAttribute = xmlDoc.CreateAttribute("templateDirectory");
           templateDirectoryAttribute.Value = TemplateDirectory;
           settingsNode.Attributes.Append(templateDirectoryAttribute);

           XmlAttribute webApplicationAttribute = xmlDoc.CreateAttribute("webApplication");
           webApplicationAttribute.Value = WebApplication;
           settingsNode.Attributes.Append(webApplicationAttribute);

           XmlAttribute parentSiteAttribute = xmlDoc.CreateAttribute("parentSite");
           parentSiteAttribute.Value = ParentSite;
           settingsNode.Attributes.Append(parentSiteAttribute);
        }

        public void Deserialize(XmlNode inputParentNode)
        {
           XmlNode settingsNode = inputParentNode.SelectSingleNode("settings");

           try
           {
              TemplateDirectory = null;
              TemplateDirectory = settingsNode.Attributes["templateDirectory"].Value;
           }
           catch (Exception)
           { }

           try
           {
              WebApplication = null;
              WebApplication = settingsNode.Attributes["webApplication"].Value;
           }
           catch (Exception)
           { }

           try
           {
              ParentSite = null;
              ParentSite = settingsNode.Attributes["parentSite"].Value;
           }
           catch (Exception)
           { }
        }

        #endregion

        #region Private Methods
        /// <summary>
        /// This will create the main site of the portal. All product libraries, 
        /// lists and so on will be below that site.
        /// </summary>
        /// <returns>true if successful, false otherwise</returns>
        private bool CreateMainSite()
        {
            try
            {
                _log.AddEntry("Generating SharePoint portal ...");

                _log.AddEntry(" Web application: " + _webApplication);
                _log.AddEntry(" Parent site: " + _parentSite);
                _log.AddEntry(" Site name: " + _siteName);


                var site = new SPSite(_webApplication);

                bool siteTemplateFound = false;
                foreach (SPWebTemplate template in site.GetWebTemplates(GetLocaleID()))
                {
                    if (template.Name == Properties.Settings.Default.SharePointSiteTemplate)
                        siteTemplateFound = true;
                }

                if (!siteTemplateFound)
                    throw new ApplicationException(Resources.Language.ERROR_SITETEMPLATE_NOT_FOUND);

                SPWeb parentWeb = site.OpenWeb(_parentSite);

                SPWeb mainWeb = parentWeb.Webs.Add(_siteName,
                   _siteTitle,
                   string.Format(Resources.Language.PORTAL_DESCRIPTION, _processProvider.Name),
                   GetLocaleID(), Properties.Settings.Default.SharePointSiteTemplate, false, false);
                mainWeb.Navigation.UseShared = true;
                mainWeb.Update();
            }
            catch (Exception e)
            {
                string message = string.Format(Resources.Language.ERROR_CREATING_MAIN_SITE, e.Message);
                _log.AddEntry(message, LogEntryType.Error);
                return false;
            }

            return true;
        }

        /// <summary>
        /// This will build the project portal
        /// </summary>
        /// <returns>true if successful, false otherwise</returns>
        private bool BuildProjectPortal()
        {
            try
            {
                if (_processProvider == null)
                    throw new ApplicationException(Resources.Language.ERROR_NO_PROCESS_PROVIDER);

                // Open the web of the collabxt project page
                var site = new SPSite(GetFullSiteUrl());
                SPWeb web = site.OpenWeb();

                // Bearbeitungszustand Auswahlfeld als Websitespalte erstellen, weil es in mehreren
                // Listen der Website verwendet wird
                _log.AddEntry(Resources.Language.CREATE_PRODUCT_STATE_FIELD);
                CreateProductStateField(web);

                _log.AddEntry(Resources.Language.ADD_MILESTONES);
                AddMilestones(web);

                // DISCIPLINES START
                var productsByDiscipline = new Dictionary<string, List<IProduct>>();
                foreach (var product in _processProvider.Products)
                {
                    if (!productsByDiscipline.ContainsKey(product.DisciplineId))
                    {
                        productsByDiscipline[product.DisciplineId] = new List<IProduct>();
                    }
                    productsByDiscipline[product.DisciplineId].Add(product);
                }

                var productUrlsByProductId = new Dictionary<string, string>();

                foreach(var discipline in _processProvider.Disciplines)
                {
                    string disciplineName = discipline.Name;

                    // Erstelle Unterwebsite basierend auf der Websitevorlage VMODELL#2
                    _log.AddEntry(Resources.Language.CREATE_SUBWEB_FOR_DISCIPLINE + disciplineName);
                    SPWeb groupWeb = web.Webs.Add(RemoveGermanCharacters(disciplineName.Replace(" ", "")), disciplineName,
                       "", GetLocaleID(), Properties.Settings.Default.SharePointDisciplineSiteTemplate, false, false);
                    groupWeb.Navigation.UseShared = true;

                    // Die Liste Produktbibliothek exisitert bereits. Sie ist in der Websitevorlage definiert.
                    var docLib = (SPDocumentLibrary)groupWeb.Lists[CollabXTStrings.Strings.ProductLibraryListName];

                    // Erweitere default DocLib um das Bearbeitungszustand-Feld
                    SPField field = web.AvailableFields[CollabXTStrings.Strings.ProductStateFieldName];
                    docLib.Fields.Add(field);

                    // Erweitere die default DocLib um ein Lookup feld auf die Meileinsteine
                    string milestonesLookupFieldName =
                        docLib.Fields.Add(CollabXTStrings.Strings.ProductDocLibMilestoneFieldName,
                        SPFieldType.MultiChoice,
                        false);
                    var milestoneChoiceField =
                        docLib.Fields[milestonesLookupFieldName] as SPFieldMultiChoice;
                   if (milestoneChoiceField != null)
                   {
                      milestoneChoiceField.Choices.AddRange(
                         _processProvider.Milestones.ConvertAll(m => m.Name).ToArray());
                      milestoneChoiceField.Update();
                   }

                   // Einträge in die Dokumentbibliothek haben eine integer id
                    // Die wird gebraucht, um die Einträge später zu referenzieren
                    // Achtung, die Id fängt bei 1 an
                    int productCounterInSharePoint = 1;

                    // Füge alle Produkte einer Gruppe der Produktbibliothek der neuen
                    // Unterwebsite hinzu
                    foreach (var prod in productsByDiscipline[discipline.Id])
                    {
                        // Wenn das Produkt keine Vorlage hat, passiert momentan nichts.
                        if (prod.HasDocumentTemplate)
                        {
                            string milestoneReferencesForProduct = GetMilestoneReferencesForProduct(prod);

                            string productUrl = UploadProductTemplate(discipline, groupWeb,
                                prod, productCounterInSharePoint,
                                milestoneReferencesForProduct);
                            productUrlsByProductId[prod.Id] = productUrl;
                            productCounterInSharePoint++;
                        }
                        else
                            _log.AddEntry(
                                string.Format("\t" + Resources.Language.WARNING_NO_PRODUCT_TEMPLATE, prod.Name), 
                                LogEntryType.Warning);
                    }
                    // Hinzufügen von Produkten zur Produktbibliothek der Unterwebsite abgeschlossen
                    docLib.Update();

                    AddDisciplineDescription(discipline, groupWeb);
                }
                // DISCIPLINES END

                _log.AddEntry(Resources.Language.ADD_WEBPARTS);
                AddCollabXTWebParts(web);

                _log.AddEntry(Resources.Language.ADD_ACTIVITIES);
                AddActivitiesToTaskList(web, productUrlsByProductId);
            }
            catch (Exception e)
            {
                string message = string.Format(Resources.Language.ERROR_CREATING_PORTAL, e.Message);
                _log.AddEntry(message, LogEntryType.Error);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Creates a new web field of type Choice. It will be initialized with the
        /// product states as defined in the V-Modell XT.
        /// </summary>
        /// <param name="web">The site to add the new field to.</param>
        private static void CreateProductStateField(SPWeb web)
        {
            // Product State
            string newFieldName = web.Fields.Add(CollabXTStrings.Strings.ProductStateFieldName,
               SPFieldType.Choice, true);

            var productStateField = (SPFieldChoice)web.Fields[newFieldName];
            productStateField.Choices.Add(CollabXTStrings.Strings.ProductStateSubmitted);
            productStateField.Choices.Add(CollabXTStrings.Strings.ProductStateFinished);
            productStateField.Choices.Add(CollabXTStrings.Strings.ProductStateInProcessing);
            productStateField.DefaultValue = CollabXTStrings.Strings.ProductStateInProcessing;
            productStateField.Update();
        }

        /// <summary>
        /// Creates a string containing all milestones that the given product participates in.
        /// The string will have a format that can be understood by the SharePoint MultiChoice
        /// field.
        /// </summary>
        /// <param name="prod">The product for which to get the milestones string</param>
        /// <returns>
        /// The milestones that the given product belongs to. The string will have the MultiChoice
        /// field format.
        /// </returns>
        private string GetMilestoneReferencesForProduct(IProduct prod)
        {
            string referenceString = ";#";

            foreach(IMilestone curMilestone in _processProvider.GetMilestonesByProductID(prod.Id))
                referenceString += curMilestone.Name + ";#";

            return referenceString;
        }

        /// <summary>
        /// Creates a new Milestones list in the given site and adds all milestones found in the
        /// input process to that list.
        /// </summary>
        /// <param name="web">The site that shall contain the milestones list</param>
        private void AddMilestones(SPWeb web)
        {
            // Erstelle Meilensteinliste
            Guid milestonesListGuid = web.Lists.Add(CollabXTStrings.Strings.MilestonesListName,
               CollabXTStrings.Strings.MilestonesListDescription, SPListTemplateType.GenericList);
            SPList milestonesList = web.Lists[milestonesListGuid];

            // Erweitere Meileinsteinliste um Datumsfeld
            string dateFieldName = milestonesList.Fields.Add(CollabXTStrings.Strings.MilestonesListScheduledDateFieldName,
                SPFieldType.DateTime, false);
            milestonesList.Fields[dateFieldName].Update();

            // Erweitere Meilensteinliste um Nummer-Feld
            string numberFieldName =
              milestonesList.Fields.Add(CollabXTStrings.Strings.MilestonesListNumberFieldName,
              SPFieldType.Integer,
              true);
            milestonesList.Fields[numberFieldName].Update();

            // Erweitere Meilensteinliste um Beschreibungs-Feld
            string descriptionFieldName =
              milestonesList.Fields.Add(CollabXTStrings.Strings.MilestonesListDescriptionFieldName,
              SPFieldType.Note,
              false);
            milestonesList.Fields[descriptionFieldName].Update();

            foreach (var milestone in _processProvider.Milestones)
            {
                SPListItem item = milestonesList.Items.Add();
                item[CollabXTStrings.Strings.MilestonesListTitleFieldName] = milestone.Name;
                item[CollabXTStrings.Strings.MilestonesListScheduledDateFieldName] = milestone.ScheduledDate;
                item[CollabXTStrings.Strings.MilestonesListNumberFieldName] = milestone.ScheduleNumber;
                item.Update();
            }
        }

        /// <summary>
        /// Adds all activities given in the input process to the SharePoint task list.
        /// </summary>
        /// <param name="web">The site that contains the task list</param>
        /// <param name="productUrlsByProductId">
        /// A dictionary mapping product Ids to the product URL in the SharePoint portal.
        /// This is necessary to establish links between the activities and the products that the activities
        /// work on.
        /// </param>
        private void AddActivitiesToTaskList(SPWeb web, IDictionary<string, string> productUrlsByProductId)
        {
            SPList tasksList = web.Lists[CollabXTStrings.Strings.TasksListName];

            // Erweitere die Standard-Aufgabenliste um eine URL Spalte mit dem Verweis auf das
            // bearbeitete Produkt.
            // [GKa TODO: was besseres ueberlegen. URLs sind bloed. Besser wäre ne Art Lookup. Das geht
            // aber nicht über mehrere Listen in mehreren (Unter-)Seiten]
            string produktRefFieldName =
               tasksList.Fields.Add(CollabXTStrings.Strings.TasksListProductFieldName,
               SPFieldType.URL,
               false);
            tasksList.Fields[produktRefFieldName].Update();

            // add all activities to the SharePoint task list...
            foreach (var activity in _processProvider.Activities)
            {
                SPListItem item = tasksList.Items.Add();
                item[CollabXTStrings.Strings.TasksListTitleFieldName] = activity.Name;
                //item[CollabXTStrings.Strings.TasksListStartDateFieldName] = ;
                //item[CollabXTStrings.Strings.TasksListDueDateFieldName] = ;
                // [GKa TODO: benutzer abhängig von der rolle der aktivität setzen]
                item[CollabXTStrings.Strings.TasksListBodyFieldName] = activity.Description;

                foreach (var dependency in _processProvider.Dependencies)
                {
                    if (dependency is ProductActivityDependency)
                    {
                        var productActivityDependency = 
                            dependency as ProductActivityDependency;
                        if (productActivityDependency.ActivityId == activity.Id)
                        {
                            if (productUrlsByProductId.ContainsKey(productActivityDependency.ProductId))
                            {
                                string productUrl = productUrlsByProductId[productActivityDependency.ProductId];
                                item[produktRefFieldName] = productUrl;
                            }
                        }
                    }
                }

                item.Update();
            }
        }

        /// <summary>
        /// Adds the CollabXT specific webparts to the given site
        /// </summary>
        /// <param name="web">The site to add the webparts to</param>
        private static void AddCollabXTWebParts(SPWeb web)
        {
            // WebPartManager der aktuellen Unterwebsite holen
            SPLimitedWebPartManager wpMngr =
               web.GetLimitedWebPartManager("default.aspx",
               System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

            var ppPart = new CollabXTWebParts.ProjektplanWebPart
                            {
                               Title = CollabXTStrings.Strings.ProjectPlanWebPartTitle
                            };
           wpMngr.AddWebPart(ppPart, "Left", 3);
            var poPart = new CollabXTWebParts.ProjektOverviewWebPart
                            {
                               Title = CollabXTStrings.Strings.ProjectOverviewWebPartTitle
                            };
           wpMngr.AddWebPart(poPart, "Right", 3);
        }

        struct ProductTemplate
        {
            public string FileExtension;
            public byte[] Bytes;
        }

        /// <summary>
        /// Uploads the product template file for the given product into the
        /// document library of the given site
        /// </summary>
        /// <param name="discipline">
        /// The discipline that the product belongs to
        /// </param>
        /// <param name="groupWeb">
        /// The site that contains the document library that the
        /// product template will be uploaded to
        /// </param>
        /// <param name="prod">
        /// The product for which to upload the product template
        /// </param>
        /// <param name="productNumberInSharePoint">
        /// The integer Id of the given product in the
        /// document library list in SharePoint. This Id will be part of the URL string to
        /// reference the given product. It is necessary to be able to establish links to this
        /// product
        /// </param>
        /// <param name="milestoneReferences">
        /// A string containing the milestones that this product belongs to.
        /// The string has to be in the format that can be understood by the SharePoint MultiChoice
        /// field. <see cref="GetMilestoneReferencesForProduct"/>
        /// </param>
        /// <returns>
        /// The URL string of the given product. This can be used to reference the product
        /// in the portal.
        /// </returns>
        private string UploadProductTemplate(IDiscipline discipline, SPWeb groupWeb, 
            IProduct prod, int productNumberInSharePoint, string milestoneReferences)
        {
            var docLib = 
                groupWeb.Lists[CollabXTStrings.Strings.ProductLibraryListName] as SPDocumentLibrary;

            // Lese .rtf Produktvorlage ein
            ProductTemplate template = ReadProductTemplateFile(prod, discipline.Name);
            if (template.Bytes == null)
                return null;

           if (docLib != null)
           {
              string docUrl = docLib.RootFolder + "/" + RemoveGermanCharacters(prod.Name) + template.FileExtension;
              _log.AddEntry("\t" + Resources.Language.ADD_PRODUCT_TEMPLATE + docUrl);

              // Füge .rtf zur Produktbibliothek hinzu
              SPFile file = docLib.RootFolder.Files.Add(docUrl, template.Bytes, true);
              SPListItem fileItem = file.Item;

              fileItem[CollabXTStrings.Strings.ProductDocLibMilestoneFieldName] = milestoneReferences;

              fileItem.Update();

              // merke die URL zum neu hinzugefügten Produkt [GKa TODO: das ist nicht so
               // prickelnd mit so fest verdrahteten URLs. Was anderes überlegen.]
               return string.Format("{0}/{1}/Forms/DispForm.aspx?ID={2}, {3}", groupWeb.Url,
                                              docLib.RootFolder, productNumberInSharePoint, prod.Name);
           }

            return null;
        }

        /// <summary>
        /// Fills the ContentEditorWebPart that is preconfigured in the VMODELL#2
        /// site template with the description of the given discipline
        /// </summary>
        /// <param name="discipline">The discipline that the given site represents</param>
        /// <param name="groupWeb">The site representing the discipline</param>
        private static void AddDisciplineDescription(IDiscipline discipline, SPWeb groupWeb)
        {
            // WebPartManager der aktuellen Unterwebsite holen
            SPLimitedWebPartManager wpManager =
               groupWeb.GetLimitedWebPartManager("default.aspx",
               System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

            // Die Websitevorlage VMODELL#2 fügt einen ContentEditorWebPart zur Site hinzu...
            ContentEditorWebPart wpPurpose = null;
            foreach (System.Web.UI.WebControls.WebParts.WebPart part in wpManager.WebParts)
            {
                // Identifikation nur anhand des Typs ist bisschen schwach
                if (part.GetType() == typeof(ContentEditorWebPart))
                {
                    // ... here it is.
                    wpPurpose = (ContentEditorWebPart)part;
                    break;
                }
            }

            // Fill ContentEditorWebPart with the description text of the current discipline...
            if (wpPurpose != null)
            {
                // Create an XmlElement to hold the value of the Content property.
                var xmlDoc = new XmlDocument();
                XmlElement xmlElement = xmlDoc.CreateElement("Content");
                xmlElement.InnerText = discipline.Description;

                wpPurpose.Content = xmlElement;
            }
            // ...and save changes
            wpManager.SaveChanges(wpPurpose);

            groupWeb.Update();
        }

        /// <summary>
        /// Returns the language id of the current language.
        /// So far, only German and English are supported
        /// </summary>
        /// <returns>The language Id of the current language</returns>
        private static uint GetLocaleID()
        {
            System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;
            if (ci.LCID == 1033 || ci.LCID == 1031)
                return (uint)ci.LCID;
            // TODO: maybe better explode in case of unknown culture
            return 1033;
        }

        /// <summary>
        /// Returns the full URL of the SharePoint portal that
        /// will be generated with this tool provider
        /// </summary>
        /// <returns>The URL of the portal to be created</returns>
        private string GetFullSiteUrl()
        {
            return _webApplication + _parentSite + "/" + _siteName;
        }

        /// <summary>
        /// Replaces all german characters in the input string and replaces them 
        /// with "non-umlaut letters".
        /// So, ä becomes ae for example
        /// </summary>
        /// <param name="str">
        /// The string that should be stripped of umlauts
        /// </param>
        /// <returns>
        /// The input string <paramref name="str"/> but stripped of german characters.
        /// </returns>
        private static string RemoveGermanCharacters(string str)
        {
            string temp = str;
            temp = temp.Replace("Ä", "Ae");
            temp = temp.Replace("Ü", "Ue");
            temp = temp.Replace("Ö", "Oe");
            temp = temp.Replace("ä", "ae");
            temp = temp.Replace("ü", "ue");
            temp = temp.Replace("ö", "oe");
            temp = temp.Replace("ß", "ss");
            temp = temp.Replace("/", " ");
            return temp;
        }

        /// <summary>
        /// Reads the template rtf file for the product specified in <paramref name="prod"/>
        /// and returns it as an array of byte.
        /// </summary>
        /// <param name="prod">
        /// The product for which to load the template file.
        /// </param>
        /// <param name="disciplineName">
        /// The discipline that the product belongs to.
        /// </param>
        /// <returns>
        /// The product template file as byte array. 
        /// </returns>
        private ProductTemplate ReadProductTemplateFile(IProduct prod, string disciplineName)
        {
            var template = new ProductTemplate();

            // Beispiel fuer ein Produkt wo diese Ersetzung noetig ist
            // Produktname: Problem-/Änderungsbewertung
            // Dateiname: Problem-Änderungsbewertung.rtf
            string name = prod.Name.Replace("/", "");

            string[] candidateFiles = Directory.GetFiles(_templateDirectory + @"\" + disciplineName,
                name + @".*");

            if (candidateFiles.Length <= 0)
            {
                _log.AddEntry("\t" + Resources.Language.ERROR_NO_TEMPLATE + prod.Name, LogEntryType.Error);
                return template;
            }
           
           if (candidateFiles.Length > 1)
           {
              _log.AddEntry("\t" + Resources.Language.WARNING_MULTIPLE_TEMPLATES + prod.Name, LogEntryType.Warning);
           }

           byte[] bytes;
            using (var reader = new BinaryReader(File.Open(candidateFiles[0], FileMode.Open, FileAccess.Read)))
            {
                bytes = reader.ReadBytes((int)reader.BaseStream.Length);
                reader.Close();
            }

            template.Bytes = bytes;
            template.FileExtension = Path.GetExtension(candidateFiles[0]);
            return template;
        }

        #endregion

    }
}
